media_pp\elements\filter/
pacer.rs1use std::{
2 collections::VecDeque,
3 sync::Arc,
4 thread,
5 time::{Duration, Instant},
6};
7
8use crate::pp_log::{PpLog, pp_info};
9use ffmpeg_next as ffmpeg;
10use thiserror::Error as ThisError;
11
12use crate::{
13 buffer::MediaBuffer,
14 clock::Clock,
15 control::ControlMsg,
16 element::{Element, ElementType, Sink, Source, element_pp_log},
17 pad::SrcPad,
18 time::{InvalidTimeBase, MediaTimestamp, TimeBase},
19};
20
21#[derive(Debug, ThisError)]
23pub enum PacerError {
24 #[error(
30 "invalid time base {numerator}/{denominator}: both numerator and denominator must be positive"
31 )]
32 InvalidTimeBase { numerator: i32, denominator: i32 },
33
34 #[error("pts {pts} is too far from this pacer's first pts {first_pts} to pace against")]
39 TimestampDeltaOverflow { pts: i64, first_pts: i64 },
40}
41
42fn nanoseconds() -> TimeBase {
45 TimeBase::new_unchecked(ffmpeg::Rational::new(1, 1_000_000_000))
46}
47
48const INTERRUPT_POLL_INTERVAL: Duration = Duration::from_millis(10);
51
52pub struct Pacer {
70 pp_log: PpLog,
71 name: Arc<str>,
72 time_base: TimeBase,
73 clock: Arc<Clock>,
74 first_pts: Option<i64>,
83 interrupt_epoch: u64,
90 pending: VecDeque<MediaBuffer>,
94 pad: SrcPad,
95}
96
97impl Pacer {
98 pub fn new(
99 name: impl Into<String>,
100 time_base: ffmpeg::Rational,
101 clock: Arc<Clock>,
102 ) -> Result<Self, PacerError> {
103 let name: Arc<str> = name.into().into();
104 let pp_log = element_pp_log(ElementType::Pacer, &name, None);
105 pp_info!(pp_log: &pp_log, "created: time_base={time_base}");
106 let pad = SrcPad::new(format!("{name}_src"));
107 let interrupt_epoch = clock.interrupt_epoch();
108 let time_base = TimeBase::try_new(time_base).map_err(
109 |InvalidTimeBase {
110 numerator,
111 denominator,
112 }| PacerError::InvalidTimeBase {
113 numerator,
114 denominator,
115 },
116 )?;
117 Ok(Self {
118 name,
119 pp_log,
120 time_base,
121 clock,
122 first_pts: None,
123 interrupt_epoch,
124 pending: VecDeque::new(),
125 pad,
126 })
127 }
128
129 fn wait_for(&mut self, pts: Option<i64>) -> Result<bool, PacerError> {
139 if self.clock.interrupt_epoch() != self.interrupt_epoch {
140 return Ok(false);
141 }
142 let Some(pts) = pts else { return Ok(true) };
143 let first_pts = *self.first_pts.get_or_insert(pts);
144
145 let elapsed_ticks = pts
146 .checked_sub(first_pts)
147 .ok_or(PacerError::TimestampDeltaOverflow { pts, first_pts })?;
148 if elapsed_ticks <= 0 {
149 return Ok(true);
150 }
151 let elapsed_ns = MediaTimestamp::new_unchecked(elapsed_ticks, self.time_base)
156 .rescale(nanoseconds())
157 .max(0) as u64;
158
159 let due = self.clock.start() + Duration::from_nanos(elapsed_ns);
160 loop {
161 if self.clock.interrupt_epoch() != self.interrupt_epoch {
162 return Ok(false);
163 }
164 let now = Instant::now();
165 if due <= now {
166 return Ok(true);
167 }
168 thread::sleep((due - now).min(INTERRUPT_POLL_INTERVAL));
169 }
170 }
171}
172
173impl Element for Pacer {
174 fn name(&self) -> Arc<str> {
175 self.name.clone()
176 }
177
178 fn element_type(&self) -> ElementType {
179 ElementType::Pacer
180 }
181
182 fn pp_log(&self) -> &PpLog {
183 &self.pp_log
184 }
185
186 fn pp_log_mut(&mut self) -> &mut PpLog {
187 &mut self.pp_log
188 }
189}
190
191impl Source for Pacer {
192 fn src_pads(&mut self) -> &mut [SrcPad] {
193 std::slice::from_mut(&mut self.pad)
194 }
195}
196
197impl Sink for Pacer {
198 fn consume(&mut self, buf: MediaBuffer) -> crate::error::Result<()> {
199 self.pending.push_back(buf);
200 while let Some(buf) = self.pending.pop_front() {
201 let ready = match &buf {
202 MediaBuffer::Packet(packet) => self.wait_for(packet.pts())?,
203 MediaBuffer::Video(frame) => self.wait_for(frame.pts())?,
204 MediaBuffer::Audio(frame) => self.wait_for(frame.pts())?,
205 MediaBuffer::Eos => true,
206 };
207 if !ready {
208 self.pending.push_front(buf);
209 return Ok(());
210 }
211 self.pad.push(buf)?;
212 }
213 Ok(())
214 }
215
216 fn control(&mut self, msg: ControlMsg) -> crate::error::Result<()> {
217 self.interrupt_epoch = self.clock.interrupt_epoch();
221 match msg {
222 ControlMsg::Seek(_) => {
223 self.pending.clear();
224 self.first_pts = None;
225 self.clock.reset();
226 }
227 ControlMsg::Stop => self.pending.clear(),
228 ControlMsg::Pause | ControlMsg::Resume => {}
229 }
230 self.pad.control(msg)
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use std::{sync::mpsc, time::Duration};
238
239 fn packet(pts: i64) -> MediaBuffer {
240 let mut packet = ffmpeg::Packet::empty();
241 packet.set_pts(Some(pts));
242 MediaBuffer::Packet(Arc::new(packet))
243 }
244
245 #[test]
246 fn long_wait_returns_promptly_when_control_interrupts_it() {
247 let clock = Arc::new(Clock::new());
248 let mut pacer = Pacer::new("pacer", ffmpeg::Rational::new(1, 1), clock.clone()).unwrap();
249 assert!(
250 pacer.wait_for(Some(0)).unwrap(),
251 "first pts should establish the anchor"
252 );
253
254 let (started_tx, started_rx) = mpsc::channel();
255 let worker = thread::spawn(move || {
256 started_tx.send(()).expect("test receiver alive");
257 pacer.wait_for(Some(60))
258 });
259
260 started_rx.recv().expect("paced wait should start");
261 thread::sleep(Duration::from_millis(20));
262 clock.interrupt();
263
264 assert!(
265 !worker
266 .join()
267 .expect("paced wait should return")
268 .expect("interrupted wait is Ok(false), not an error"),
269 "an interrupted paced wait must return before its due time"
270 );
271 }
272
273 #[test]
274 fn pause_retains_interrupted_buffer_but_seek_and_stop_discard_it() {
275 let clock = Arc::new(Clock::new());
276 let mut pacer = Pacer::new("pacer", ffmpeg::Rational::new(1, 1), clock.clone()).unwrap();
277
278 clock.interrupt();
279 pacer.consume(packet(0)).expect("interrupted consume");
280 assert_eq!(pacer.pending.len(), 1);
281
282 pacer.control(ControlMsg::Pause).expect("pause");
283 assert_eq!(pacer.pending.len(), 1, "pause must retain the buffer");
284
285 pacer
286 .control(ControlMsg::Seek(Duration::ZERO))
287 .expect("seek");
288 assert!(pacer.pending.is_empty(), "seek must discard stale data");
289
290 clock.interrupt();
291 pacer.consume(packet(1)).expect("interrupted consume");
292 assert_eq!(pacer.pending.len(), 1);
293 pacer.control(ControlMsg::Stop).expect("stop");
294 assert!(pacer.pending.is_empty(), "stop must abandon pending data");
295 }
296
297 #[test]
298 fn new_rejects_an_invalid_time_base() {
299 let clock = Arc::new(Clock::new());
300 for rational in [
301 ffmpeg::Rational::new(0, 1),
302 ffmpeg::Rational::new(1, 0),
303 ffmpeg::Rational::new(-1, 1),
304 ffmpeg::Rational::new(1, -1),
305 ] {
306 assert!(
307 matches!(
308 Pacer::new("pacer", rational, clock.clone()),
309 Err(PacerError::InvalidTimeBase { .. })
310 ),
311 "expected {rational} to be rejected"
312 );
313 }
314 }
315
316 #[test]
324 fn a_pathological_pts_jump_is_a_typed_error_not_silent_passthrough() {
325 let clock = Arc::new(Clock::new());
326 let mut pacer = Pacer::new("pacer", ffmpeg::Rational::new(1, 1), clock).unwrap();
327
328 assert!(pacer.consume(packet(-1)).is_ok(), "establishes first_pts");
329
330 let error = pacer
331 .consume(packet(i64::MAX))
332 .expect_err("pts far enough from first_pts to overflow the subtraction");
333 assert!(matches!(
334 error,
335 crate::Error::PacerError(PacerError::TimestampDeltaOverflow {
336 pts: i64::MAX,
337 first_pts: -1,
338 })
339 ));
340 assert!(
341 pacer.pending.is_empty(),
342 "the overflowing buffer must not get stuck in `pending`"
343 );
344
345 assert!(pacer.wait_for(Some(0)).is_ok());
349 }
350}